package org.future.wsdl.data;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;

/**
 * wsdl:definitions 元素信息
 * @author dycc
 *
 */
public class WsdlDefinitions {
    private Map<String,String> namespaces = new HashMap<String,String>();
    
    private String targetNamespace;
    
    private String documentation;
    
    private List<WsdlService> services = new ArrayList<WsdlService>();
    
    private List<WsdlBinding> bindings = new ArrayList<WsdlBinding>();
    
    private List<WsdlPortType> portTypes = new ArrayList<WsdlPortType>();
    
    private List<WsdlMessage> messages = new ArrayList<WsdlMessage>();
    
    private WsdlTypes types;
    
    // namespaces
    public Map<String, String> getNamespaces() {
        return namespaces;
    }
    public void setNamespaces(Map<String, String> namespaces) {
        this.namespaces = namespaces;
    }
    // targetNamespace
    public String getTargetNamespace() {
        return targetNamespace;
    }
    public void setTargetNamespace(String targetNamespace) {
        this.targetNamespace = targetNamespace;
    }
    // documentation
    public String getDocumentation() {
        return documentation;
    }
    public void setDocumentation(String documentation) {
        this.documentation = documentation;
    }
    // services
    public List<WsdlService> getServices() {
        return services;
    }
    public void setServices(List<WsdlService> services) {
        this.services = services;
    }
    // bindings
    public List<WsdlBinding> getBindings() {
        return bindings;
    }
    public void setBindings(List<WsdlBinding> bindings) {
        this.bindings = bindings;
    }
    // portTypes
    public List<WsdlPortType> getPortTypes() {
        return portTypes;
    }
    public void setPortTypes(List<WsdlPortType> portTypes) {
        this.portTypes = portTypes;
    }
    // messages
    public List<WsdlMessage> getMessages() {
        return messages;
    }
    public void setMessages(List<WsdlMessage> messages) {
        this.messages = messages;
    }
    // types
    public WsdlTypes getTypes() {
        return types;
    }
    public void setTypes(WsdlTypes types) {
        this.types = types;
    }
    /**
     * set the relationship between binding and port
     */
    public void setBind2Port(){
        List<WsdlService> list_wsdl_serv = this.getServices();
        for(int i=0;i<list_wsdl_serv.size();i++){
            WsdlService wsdl_serv = list_wsdl_serv.get(i);
            List<WsdlPort> list_port = wsdl_serv.getPorts();
            for(int idx=0;idx<list_port.size();idx++){
                WsdlPort wsdl_port = list_port.get(idx);
                String bindingName = wsdl_port.getBinding().getLocalPart();
                WsdlBinding wsdl_bind = getBindingByName(bindingName);
                wsdl_port.setWsdlBinding(wsdl_bind);
                wsdl_bind.setWsdlPort(wsdl_port);
            }
        }
    }
    /**
     * get WsdlBinding by bindingName
     * @param bindingName
     * @return
     */
    public WsdlBinding getBindingByName(String bindingName){
        List<WsdlBinding> list_wsdl_bind = this.getBindings();
        for(int i=0;i<list_wsdl_bind.size();i++){
            WsdlBinding wsdl_bind = list_wsdl_bind.get(i);
            if(wsdl_bind.getName().endsWith(bindingName)){
                return wsdl_bind;
            }
        }
        return null;
    }
    /**
     * set the relationship between portType and binding
     */
    public void setPortType2Bind(){
        List<WsdlBinding> list_wsdl_bind = this.getBindings();
        for(int i=0;i<list_wsdl_bind.size();i++){
            WsdlBinding wsdl_bind = list_wsdl_bind.get(i);
            String ptName = wsdl_bind.getType().getLocalPart();
            WsdlPortType wsdl_portType = getPortTypeByName(ptName);
            wsdl_bind.setWsdlPortType(wsdl_portType);
            wsdl_portType.getWsdlBindings().add(wsdl_bind);
        }
    }
    /**
     * get portType by name
     * @param portTypeName
     * @return
     */
    public WsdlPortType getPortTypeByName(String portTypeName){
        List<WsdlPortType> list_wsdl_portType = this.getPortTypes();
        for(int i=0;i<list_wsdl_portType.size();i++){
            WsdlPortType wsdl_portType = list_wsdl_portType.get(i);
            if(wsdl_portType.getName().equals(portTypeName)){
                return wsdl_portType;
            }
        }
        return null;
    }
    /**
     * set the relationship between message and operation
     */
    public void setMessage2Operation(){
        List<WsdlPortType> list_wsdl_portType = this.getPortTypes();
        for(int i=0;i<list_wsdl_portType.size();i++){
            WsdlPortType wsdl_portType = list_wsdl_portType.get(i);
            List<WsdlOperation> list_wsdl_oper = wsdl_portType.getOperations();
            for(int idx=0;idx<list_wsdl_oper.size();idx++){
                WsdlOperation wsdl_oper = list_wsdl_oper.get(idx);
                QName qName = wsdl_oper.getInMsgQName();
                if(qName != null){
                    WsdlMessage wsdl_msg = getMessageByName(qName.getLocalPart());
                    wsdl_oper.setInWsdlMessage(wsdl_msg);
                    wsdl_msg.setWsdlOperation(wsdl_oper);
                }
                qName = wsdl_oper.getOutMsgQName();
                if(qName != null){
                    WsdlMessage wsdl_msg = getMessageByName(qName.getLocalPart());
                    wsdl_oper.setOutWsdlMessage(wsdl_msg);
                    wsdl_msg.setWsdlOperation(wsdl_oper);
                }
                List<WsdlFault> list_wsdl_fault = wsdl_oper.getFaults();
                for(int k=0;k<list_wsdl_fault.size();k++){
                    WsdlFault wsdl_fault = list_wsdl_fault.get(k);
                    qName = wsdl_fault.getMsgQName();
                    if(qName != null){
                        WsdlMessage wsdl_msg = getMessageByName(qName.getLocalPart());
                        wsdl_oper.getFaultsMessage().add(wsdl_msg);
                        wsdl_msg.setWsdlOperation(wsdl_oper);
                    }
                }// end for faults
            }// end for operations
        }// end for portTypes
    }
    /**
     * get message by name
     * @param name
     * @return
     */
    public WsdlMessage getMessageByName(String name){
        List<WsdlMessage> list_msg = this.getMessages();
        for(int i=0;i<list_msg.size();i++){
            WsdlMessage msg = list_msg.get(i);
            if(msg.getName().equals(name)){
                return msg;
            }
        }
        return null;
    }
    /**
     * set the relationship between element and operation
     */
    public void setElement2Operation(){
        // get the only one portType in current wsdl document
        WsdlPortType portType = this.getPortTypes().get(0);
        List<WsdlOperation> list_oper = portType.getOperations();
        // set input parameter ant return type
        for(int i=0;i<list_oper.size();i++){
            WsdlOperation wsdl_oper = list_oper.get(i);
            QName inQName = wsdl_oper.getInMsgQName();
            QName outQName = wsdl_oper.getOutMsgQName();
            if(inQName != null){
                List<XElementInner> list_in_param = getElementsByMsgName(inQName.getLocalPart());
                wsdl_oper.setInParameters(list_in_param);
                for(int k=0;k<list_in_param.size();k++){
                    XElementInner inner_elem = list_in_param.get(k);
                    inner_elem.setWsdlOperation(wsdl_oper);
                }
            }
            if(outQName != null){
                List<XElementInner> list_returnType = getElementsByMsgName(outQName.getLocalPart());
                if(list_returnType.size() > 0){
                    wsdl_oper.setReturnType(list_returnType.get(0));
                    list_returnType.get(0).setWsdlOperation(wsdl_oper);
                }
            }
            List<WsdlFault> list_wsdl_fault = wsdl_oper.getFaults();
            for(int k=0;k<list_wsdl_fault.size();k++){
                WsdlFault wsdl_fault = list_wsdl_fault.get(k);
                QName qName = wsdl_fault.getMsgQName();
                if(qName != null){
                    List<XElementInner> list_fault_elem = getElementsByMsgName(qName.getLocalPart());
                    for(int index=0;index<list_fault_elem.size();index++){
                        XElementInner inner_elem = list_fault_elem.get(k);
                        inner_elem.setWsdlOperation(wsdl_oper);
                    }
                }
            }// end for faults
        }
    }
    /**
     * get elements by message name
     * @param msgName
     * @return
     */
    public List<XElementInner> getElementsByMsgName(String msgName){
        if(msgName == null || msgName.equals("")){
            return new ArrayList<XElementInner>();
        }
        WsdlMessage wsdl_msg = getMessageByName(msgName);
        return getElementsByMsg(wsdl_msg);
    }
    /**
     * 返回消息所对应的参数
     * @param wsdl_msg
     * @param wsdl_defi
     * @return
     */
    public List<XElementInner> getElementsByMsg(WsdlMessage wsdl_msg){
        List<XElementInner> list = new ArrayList<XElementInner>();
        if(wsdl_msg == null || wsdl_msg.getPartName() == null){
            return list;
        }
        if(wsdl_msg.getPartEleQName() == null){
            return wsdl_msg.getInnerEleList();
        }
        // 添加 message 中所直接包含的元素
        list.addAll(wsdl_msg.getInnerEleList());
        // 查找 message 所对应的element 元素
        String ele_name = wsdl_msg.getPartEleQName().getLocalPart();
        List<XSchema> list_schm = this.getTypes().getSchemas();
        for(int i=0;i<list_schm.size();i++){
            XSchema xschema = list_schm.get(i);
            List<XComplexType> list_complex = xschema.getComplexTypeList();
            for(int cidx=0;cidx<list_complex.size();cidx++){
                XComplexType xcomp = list_complex.get(cidx);
                if(xcomp.getName().equals(ele_name)){
                    XElementInner inner_ele = new XElementInner();
                    inner_ele.setName(wsdl_msg.getPartName());
                    inner_ele.setType(xcomp.getName());
                    inner_ele.setComplexType(xcomp);
                    list.add(inner_ele);
                }
            }
            List<XElement> list_elem = xschema.getElementList();
            for(int j=0;j<list_elem.size();j++){
                XElement elem = list_elem.get(j);
                if(!elem.getName().equals(ele_name)){
                    continue;
                }
                List<XElementInner> list_inner_elem = getInnerElements(elem);
                list.addAll(list_inner_elem);
                return list;
            }
        }
        return list;
    }
    /**
     * 返回 xs:element 内所包含的所有 xs:element
     * @param elem
     * @return
     */
    private List<XElementInner> getInnerElements(XElement elem){
        List<XElementInner> list_inner_elem = elem.getInnerEleList();
        out2for:
        for(int k=0;k<list_inner_elem.size();k++){
            XElementInner elem_inner = list_inner_elem.get(k);
            if(elem_inner.isComplexType()){
                // current element refer to a complexType
                String type = elem_inner.getType();
                int commaIdx = type.indexOf(":");
                String complexName = type.substring(commaIdx + 1);
                // get all complexType
                List<XComplexType> list_complex = getComplexTypes();
                for(int pos=0;pos<list_complex.size();pos++){
                    XComplexType complex = list_complex.get(pos);
                    if(complex.getName().equals(complexName)){
                        // set refer to complexType
                        elem_inner.setComplexType(complex);
                        complex.setParentEleInner(elem_inner);
                        continue out2for;
                    }
                }// end for
            }// end if
        }// end for
        return list_inner_elem;
    }
    /**
     * get all complexType
     * @return
     */
    public List<XComplexType> getComplexTypes(){
        List<XComplexType> list = new ArrayList<XComplexType>();
        List<XSchema> list_schm = this.getTypes().getSchemas();
        for(int idx=0;idx<list_schm.size();idx++){
            XSchema xschema = list_schm.get(idx);
            List<XComplexType> list_complex = xschema.getComplexTypeList();
            list.addAll(list_complex);
        }// end for
        return list;
    }
    /**
     * get all operation
     * @return
     */
    public List<WsdlOperation> getOperations(){
        // get the only one portType in current wsdl document
        WsdlPortType portType = this.getPortTypes().get(0);
        List<WsdlOperation> list_oper = portType.getOperations();        
        return list_oper;
    }
    /**
     * set the relationship between complexType and message's element
     */
    public void setComplex2Message(){
        List<WsdlMessage> list_wsdl_msg = this.getMessages();
        for(int i=0;i<list_wsdl_msg.size();i++){
            WsdlMessage wsdl_msg = list_wsdl_msg.get(i);
            List<XElementInner> list_inner_ele = wsdl_msg.getInnerEleList();
            for(int k=0;k<list_inner_ele.size();k++){
                XElementInner elem_inner = list_inner_ele.get(k);
                if(elem_inner.isComplexType()){
                    // current element refer to a complexType
                    String type = elem_inner.getType();
                    int commaIdx = type.indexOf(":");
                    String complexName = type.substring(commaIdx + 1);
                    // get all complexType
                    List<XComplexType> list_complex = getComplexTypes();
                    for(int pos=0;pos<list_complex.size();pos++){
                        XComplexType complex = list_complex.get(pos);
                        if(complex.getName().equals(complexName)){
                            // set refer to complexType
                            elem_inner.setComplexType(complex);
                            complex.setParentEleInner(elem_inner);
                            break;
                        }
                    }// end for
                }// end if
            }// end for message inner elements list
        }// end for message
    }
    /**
     * 初始化 wsdl 文档中各元素的关系
     */
    public void initRelationship(){
        setBind2Port();
        setPortType2Bind();
        setMessage2Operation();
        setElement2Operation();
        setComplex2Message();
    }
    
    // toString
    public String toString(){
        StringBuffer sb = new StringBuffer();
        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
        sb.append("\n");
        sb.append("<wsdl:definitions");
        // add namespaces
        Iterator<String> it = namespaces.keySet().iterator();
        while(it.hasNext()){
            String prefix = it.next();
            String val = namespaces.get(prefix);
            sb.append(" xmlns:" + prefix + "=\"" + val + "\"");
        }
        // add targetNamespace
        sb.append(" targetNamespace=\"" + targetNamespace + "\"");
        sb.append(">");
        sb.append("\n");
        // add documentation
        sb.append("<wsdl:documentation>" + documentation + "</wsdl:documentation>");
        sb.append("\n");
        // add types
        sb.append(types);
        sb.append("\n");
        // add messages
        for(int i=0;i<messages.size();i++){
            sb.append(messages.get(i));
            sb.append("\n");
        }
        // add portTypes
        for(int i=0;i<portTypes.size();i++){
            sb.append(portTypes.get(i));
            sb.append("\n");
        }
        // add bindings
        for(int i=0;i<bindings.size();i++){
            sb.append(bindings.get(i));
            sb.append("\n");
        }
        // add services
        for(int i=0;i<services.size();i++){
            sb.append(services.get(i));
            sb.append("\n");
        }
        // 
        sb.append("</wsdl:definitions>");
        return sb.toString();
    }
}
